#ifdef _WIN32
#include "boinc_win.h"
#include "mpir.h"
#else
#include "config.h"
#endif

#ifndef _WIN32
#include <cstdio>
#include <cctype>
#include <ctime>
#include <cstring>
#include <cstdlib>
#include <csignal>
#include <unistd.h>
#include <gmp.h>
#endif

#include "diagnostics.h"
#include "util.h"
#include "filesys.h"
#include "boinc_api.h"
#include "mfile.h"
#include <time.h>

using std::string;

#define CHECKPOINT_FILE "MilestoneRSA_state"
#define INPUT_FILENAME "in"
#define OUTPUT_FILENAME "out"

APP_INIT_DATA uc_aid;

int do_checkpoint(mpz_t &n, mpz_t &start, mpz_t &end, mpz_t &currentNumber)
{
	char resolved_name[512];
	
	boinc_resolve_filename(CHECKPOINT_FILE, resolved_name, sizeof(resolved_name));
	FILE* state = boinc_fopen(resolved_name, "w");
	if(!state)
	{
		fprintf(stderr, "APP: MilestoneRSA: Could not open checkpoint file, resolved name %s.\n", resolved_name);
		return -1;
	}
	if(!mpz_out_str(state, 16, n) || !fputc('\n', state))
	{
		fprintf(stderr, "APP: MilestoneRSA: Could not write checkpoint file, resolved name %s.\n", resolved_name);
		fclose(state);
		return -1;
	}
	if(!mpz_out_str(state, 16, start) || !fputc('\n', state))
	{
		fprintf(stderr, "APP: MilestoneRSA: Could not write checkpoint file, resolved name %s.\n", resolved_name);
		fclose(state);
		return -1;
	}
	if(!mpz_out_str(state, 16, end) || !fputc('\n', state))
	{
		fprintf(stderr, "APP: MilestoneRSA: Could not write checkpoint file, resolved name %s.\n", resolved_name);
		fclose(state);
		return -1;
	}
	if(!mpz_out_str(state, 16, currentNumber) || !fputc('\n', state))
	{
		fprintf(stderr, "APP: MilestoneRSA: Could not write checkpoint file, resolved name %s.\n", resolved_name);
		fclose(state);
		return -1;
	}
	fclose(state);
	return 0;
}

#ifdef _WIN32

extern int main(int argc, char** argv);

int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR Args, int WinMode) {
	//LPSTR command_line;
	//char* argv[100];
	//int argc;

	//command_line = GetCommandLine();
	//argc = parse_command_line( command_line, argv );
	return main(__argc, __argv);
}
#endif

void worker() {
	int retval;
	char resolved_name[512];
	char num_str[1024];
	FILE* state, *in, *out;
	mpz_t n, start, end, remainder, currentNumber, result, totalInterval, remainingInterval;
	
	boinc_get_init_data(uc_aid);
	
	boinc_resolve_filename(CHECKPOINT_FILE, resolved_name, sizeof(resolved_name));
	
	state = boinc_fopen(resolved_name, "r");
	if (state)
	{
		if(!fgets(num_str, 1024, state))
		{
			fprintf(stderr, "APP: MilestoneRSA: invalid state file, resolved name %s.\n", resolved_name);
			fclose(state);
			exit(-1);
		}
		mpz_init_set_str(n, num_str, 16);
		
		if(!fgets(num_str, 1024, state))
		{
			fprintf(stderr, "APP: MilestoneRSA: invalid state file, resolved name %s.\n", resolved_name);
			fclose(state);
			mpz_clear(n);
			exit(-1);
		}
		mpz_init_set_str(start, num_str, 16);
		
		if(!fgets(num_str, 1024, state))
		{
			fprintf(stderr, "APP: MilestoneRSA: invalid state file, resolved name %s.\n", resolved_name);
			fclose(state);
			mpz_clear(n);
			mpz_clear(start);
			exit(-1);
		}
		mpz_init_set_str(end, num_str, 16);
		
		if(!fgets(num_str, 1024, state))
		{
			fprintf(stderr, "APP: MilestoneRSA: invalid state file, resolved name %s.\n", resolved_name);
			fclose(state);
			mpz_clear(n);
			mpz_clear(start);
			mpz_clear(end);
			exit(-1);
		}
		mpz_init_set_str(currentNumber, num_str, 16);
		fclose(state);
	}
	else
	{
		boinc_resolve_filename(INPUT_FILENAME, resolved_name, sizeof(resolved_name));
		in = boinc_fopen(resolved_name, "r");
		if (in == NULL)
		{
			fprintf(stderr, "APP: MilestoneRSA: Couldn't find input file, resolved name %s.\n", resolved_name);
			exit(-1);
		}
		else
		{
			if(!fgets(num_str, 1024, in))
			{
				fprintf(stderr, "APP: MilestoneRSA: invalid input file, resolved name %s.\n", resolved_name);
				fclose(in);
				exit(-1);
			}
			mpz_init_set_str(n, num_str, 16);

			if(!fgets(num_str, 1024, in))
			{
				fprintf(stderr, "APP: MilestoneRSA: invalid input file, resolved name %s.\n", resolved_name);
				fclose(in);
				mpz_clear(n);
				exit(-1);
			}
			mpz_init_set_str(start, num_str, 16);

			if(!fgets(num_str, 1024, in))
			{
				fprintf(stderr, "APP: MilestoneRSA: invalid input file, resolved name %s.\n", resolved_name);
				fclose(in);
				mpz_clear(n);
				mpz_clear(start);
				exit(-1);
			}
			mpz_init_set_str(end, num_str, 16);
			fclose(in);
			
			// initialize to starting value
			mpz_init_set(currentNumber, start);

			// make it uneven
			if(mpz_even_p(currentNumber))
				mpz_sub_ui(currentNumber, currentNumber, 1);
		}
	}
	
	// initialize to 1024 bit to avoid reallocations
	mpz_init2(remainder, 1024);
	mpz_init2(totalInterval,1024);
	mpz_init2(remainingInterval,1024);
	
	mpz_init_set_ui(result, 0);
	mpz_sub(totalInterval, end, start);
	double total = mpz_get_d(totalInterval);
	double remaining = total;

	/*
	long long i = 0;
	fprintf(stderr, "APP: MilestoneRSA: Entering loop with the following values:\nn=");
	mpz_out_str(stderr, 10, n);
	fprintf(stderr, "\nstart=");
	mpz_out_str(stderr, 10, start);
	fprintf(stderr, "\nend=");
	mpz_out_str(stderr, 10, start);
	fprintf(stderr, "\ntotalInterval=");
	mpz_out_str(stderr, 10, totalInterval);
	fprintf(stderr, "\ntotal=%f\n",total);
	*/

	while(mpz_cmp(currentNumber, end) < 0)
	{
		mpz_cdiv_r(remainder, n, currentNumber);
		if(mpz_sgn(remainder) == 0)
		{
			// we found something, exit and horay!
			mpz_set(result, currentNumber);
			boinc_fraction_done(1);
			break;
		}
		mpz_add_ui(currentNumber, currentNumber, 2);
		
		int checkpoint = boinc_time_to_checkpoint();
		if (checkpoint)
		{
//			fprintf(stderr, "APP: MilestoneRSA: checkpointing.\n");
			retval = do_checkpoint(n, start, end, currentNumber);
			if (retval) {
				fprintf(stderr, "APP: MilestoneRSA checkpoint failed %d\n", retval);
				mpz_clear(n);
				mpz_clear(start);
				mpz_clear(end);
				mpz_clear(remainder);
				mpz_clear(currentNumber);
				mpz_clear(result);
				mpz_clear(totalInterval);
				mpz_clear(remainingInterval);
				exit(1);
			}
			boinc_checkpoint_completed();
		}

		mpz_sub(remainingInterval, end, currentNumber);
		remaining = mpz_get_d(remainingInterval);
		double percentage = 1.0-(remaining/total);
		boinc_fraction_done(percentage);
		/*
		if(i%10000000 == 0)
		{
			fprintf(stderr, "----------\ncurrentNumber=");
			mpz_out_str(stderr, 10, currentNumber);
			fprintf(stderr, "\nremainingInterval=");
			mpz_out_str(stderr, 10, remainingInterval);
			fprintf(stderr, "\nremaining=%f\npercentage=%f\n----------\n",remaining, percentage);
		}
		i++;
		*/
	}
	
	if(mpz_sgn(result) == 0)
		fprintf(stderr, "APP: MilestoneRSA: ending, nothing found.\n");
	else
		fprintf(stderr, "APP: MilestoneRSA: ending, found a result.\n");
	
	boinc_resolve_filename(OUTPUT_FILENAME, resolved_name, sizeof(resolved_name));
	out = boinc_fopen(resolved_name, "w");
	if (out)
	{
		if(!mpz_out_str(out, 16, result))
		{
			fprintf(stderr, "APP: MilestoneRSA: Could not write output file, resolved name %s.\n", resolved_name);
			retval = -1;
		}
		else
		{
			retval = 0;
		}
		fclose(out);
	}
	else
	{
		fprintf(stderr, "APP: MilestoneRSA: Could not write output file, resolved name %s.\n", resolved_name);
		retval = -1;
	}

	mpz_clear(n);
	mpz_clear(start);
	mpz_clear(end);
	mpz_clear(remainder);
	mpz_clear(currentNumber);
	mpz_clear(result);
	mpz_clear(totalInterval);
	mpz_clear(remainingInterval);
	
	boinc_finish(retval);
}

void benchmark(void)
{
	mpz_t n,start,end,remainder,currentNumber;

	mpz_init_set_str(n, "B98E7F4EE7C2FA971E4807B619133C2859ECD24FEDE69CC6C096E7701C77F07C24FAC4C39B40C159CF8C8F9981C2FFD00C862359BC43592FA9B8591967EE0EC5C9EE401F69249610EEB7DCA96644ABB8E16AABEAC64FCF0A5140529F0B220E3C68097E28B6BCC96C3F778A8DA5582CF6AE7CFEC62612E1AFCE462CF0A1396A7F", 16);
	mpz_init(start);
	mpz_sqrt(start, n);
	mpz_init(end);
	mpz_add_ui(end, start, 100000000);
	mpz_init2(remainder, 1024);
	mpz_init_set(currentNumber, start);

	fprintf(stderr, "APP: MilestoneRSA: Starting Benchmark.\n");

	time_t startTime = time(NULL);
	while(mpz_cmp(currentNumber, end) < 0)
	{
		mpz_cdiv_r(remainder, n, currentNumber);
		mpz_add_ui(currentNumber, currentNumber, 2);
	}
	time_t endTime = time(NULL);

	fprintf(stderr, "APP: MilestoneRSA: Benchmark finished, took %li s.\n", (endTime-startTime));

	mpz_clear(n);
	mpz_clear(start);
	mpz_clear(end);
	mpz_clear(remainder);
	mpz_clear(currentNumber);
}

int main(int argc, char **argv)
{
	int i, retval;

	boinc_init_diagnostics(
		BOINC_DIAG_DUMPCALLSTACKENABLED |
		BOINC_DIAG_HEAPCHECKENABLED |
		BOINC_DIAG_REDIRECTSTDERR
	);
	
	int benchmode = 0;

	for (i=0; i<argc; i++) {
//		fprintf(stderr, "APP: MilestoneRSA: argv[%d] is %s\n", i, argv[i]);
		if (!strcmp(argv[i], "-b") || !strcmp(argv[i], "--benchmark"))
		{
			benchmode = 1;
		}
	}

	if(benchmode)
	{
		benchmark();
		return 0;
	}
	else
	{
		fprintf(stderr, "APP: MilestoneRSA: starting, argc %d\n", argc);

		retval = boinc_init();
		if (retval)
		{
//			fprintf(stderr, "APP: MilestoneRSA: boinc_init() failed with retval %d\n", retval);
			exit(retval);
		}
		worker();
	}
}
